package org.ctd.springframework.beans.factory.xml;

import com.ctd.springframework.util.xml.XmlValidationModeDetector;
import org.w3c.dom.Document;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.util.Objects;

/**
 * DefaultDocumentLoader
 * 解析 Document
 *
 * @author chentudong
 * @time 2019/3/24 22:22
 */
public class DefaultDocumentLoader implements DocumentLoader {
    /**
     * JAXP attribute used to configure the schema language for validation.
     */
    private static final String SCHEMA_LANGUAGE_ATTRIBUTE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";

    /**
     * JAXP attribute value indicating the XSD schema language.
     */
    private static final String XSD_SCHEMA_LANGUAGE = "http://www.w3.org/2001/XMLSchema";

    /**
     * 加载 Document
     *
     * @param inputSource    inputSource
     * @param entityResolver entityResolver
     * @param errorHandler   errorHandler
     * @param validationMode validationMode
     * @param namespaceAware namespaceAware
     * @return Document
     * @throws Exception Exception
     */
    @Override
    public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
        //创建解析xml的工厂
        DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);

        //创建DocumentBuilder
        DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);

        //解析输入流
        return builder.parse(inputSource);
    }

    /**
     * 创建DocumentBuilderFactory 工厂
     *
     * @param validationMode validationMode
     * @param namespaceAware namespaceAware
     * @return DocumentBuilderFactory
     * @throws ParserConfigurationException ParserConfigurationException
     */
    protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)
            throws ParserConfigurationException {
        //创建工厂
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        //
        factory.setNamespaceAware(namespaceAware);
        if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {
            factory.setValidating(true);
            //xsd
            if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
                factory.setNamespaceAware(true);
                try {
                    factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);
                } catch (IllegalArgumentException ex) {
                    ParserConfigurationException pcex = new ParserConfigurationException(
                            "Unable to validate using XSD: Your JAXP provider [" + factory +
                                    "] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " +
                                    "Upgrade to Apache Xerces (or Java 1.5) for full XSD support.");
                    pcex.initCause(ex);
                    throw pcex;
                }
            }
        }
        return factory;
    }

    /**
     * factory 创建 DocumentBuilder
     *
     * @param factory        factory
     * @param entityResolver entityResolver
     * @param errorHandler   errorHandler
     * @return DocumentBuilder
     * @throws ParserConfigurationException ParserConfigurationException
     */
    protected DocumentBuilder createDocumentBuilder(
            DocumentBuilderFactory factory, EntityResolver entityResolver, ErrorHandler errorHandler) throws ParserConfigurationException {
        //通过 factory 创建 DocumentBuilder
        DocumentBuilder docBuilder = factory.newDocumentBuilder();
        if (Objects.nonNull(docBuilder)) {
            docBuilder.setEntityResolver(entityResolver);
        }
        if (Objects.nonNull(errorHandler)) {
            docBuilder.setErrorHandler(errorHandler);
        }
        return docBuilder;
    }
}
